package com.basker.pisces.core.query.meta;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.util.StringUtils;

import com.basker.pisces.core.enums.TableType;
import com.basker.pisces.core.meta.IDataField;
import com.basker.pisces.core.meta.IField;
import com.basker.pisces.core.meta.annotation.CollectionField;
import com.basker.pisces.core.meta.annotation.DataField;
import com.basker.pisces.core.meta.annotation.ObjectField;
import com.basker.pisces.core.meta.row.RowContractMeta;
import com.basker.pisces.core.query.QueryInfo;
import com.basker.pisces.core.query.SelectField;
import com.basker.pisces.exception.ExceptionFactory;

public class QueryMeta {
    private transient AtomicInteger seq = new AtomicInteger(0);

    private transient Map<String, RowContractMeta> rowMetaMap = new HashMap<>();

    private String schema;
    private String name;
    private String displayName;
    private String mainTableName;

    private List<TableDesc> tables = new TableDescCollection(this);

    private transient TableDesc mainTable = null;
    protected transient Map<String, FieldDesc> fieldMap;
    protected transient Map<String, TableDesc> tableMap = new HashMap<String, TableDesc>();
    protected transient Map<String, TableDesc> refTableMap = new HashMap<String, TableDesc>();

    public QueryMeta() {

    }

    public QueryMeta(String name) {
        this.name = name;
    }

    public TableDesc createTableDesc(String tableName, IDataField pkField) {
        String pkFieldId = pkField != null ? pkField.getName() : null;
        String pkFieldName = pkField != null ? pkField.getDbFieldName() : null;

        return createTableDesc(tableName, pkFieldId, pkFieldName);
    }

    /**
     * 创建表，每个新创建的表会自动生成一个唯一的表别名，{@link TableDesc#getAlias()}
     *
     * @param tableName
     * @param pkFieldId
     * @param pkFieldName
     * @return
     */
    public TableDesc createTableDesc(String tableName, String pkFieldId, String pkFieldName) {
        TableDesc table = new TableDesc(tableName, pkFieldId, pkFieldName);
        table.setAlias("t" + seq.getAndIncrement());

        return table;
    }

    public String getDisplayName() {
        return displayName;
    }

    /**
     * 获取字段
     *
     * @param fieldDescId
     * @return
     */
    public FieldDesc getField(String fieldId) {
        return this.getField(fieldId, false);
    }

    /**
     * 获取字段
     *
     * @param fieldDescId
     * @param throwIfNotFound
     * @return
     */
    public FieldDesc getField(String fieldId, boolean throwIfNotFound) {
        if (fieldMap == null) {
            indexMap();
        }

        fieldId = FieldDesc.resolveSplit(fieldId);
        FieldDesc fieldDesc = fieldMap.get(fieldId);

        if (fieldDesc == null && throwIfNotFound) {
            throw ExceptionFactory.createRuntimeException("pisces-core.QueryMeta.getField-001", this.getName(),
                    fieldId);
        }

        return fieldDesc;

    }

    /**
     * 获取主表
     *
     * @return
     */
    @ObjectField
    public TableDesc getMainTable() {
        if (this.mainTable == null) {
            Optional<TableDesc> optional = this.tables.stream().filter(t -> TableType.Main.equals(t.getType()))
                    .findFirst();
            this.mainTable = optional.isPresent() ? optional.get() : null;
        }

        return this.mainTable;
    }

    public String getMainTableName() {
        return mainTableName;
    }

    @DataField
    public String getName() {
        return name;
    }

    /**
     * 获取字段关联表，如果不存在则自动创建并缓存。
     *
     * @param fieldId
     * @param tableName
     * @param pkFieldId
     * @param pkFieldName
     * @return
     */
    public TableDesc getRefTableDesc(String fieldId, String tableName, String pkFieldId, String pkFieldName) {
        String key = fieldId + "." + tableName;
        TableDesc tableDesc = this.refTableMap.get(key);

        if (tableDesc == null) {
            tableDesc = createTableDesc(tableName, pkFieldId, pkFieldName);
            tableDesc.setType(TableType.Reference);

            this.refTableMap.put(key, tableDesc);
        }

        return tableDesc;
    }

    /**
     * 根据查询器获取对象契约
     *
     * @param queryInfo
     * @return
     */
    public RowContractMeta getRowContractMeta(QueryInfo queryInfo) {
        String key = this.genRowMetaKey(queryInfo);

        RowContractMeta rowMeta = this.rowMetaMap.get(key);
        if (rowMeta == null) {
            synchronized (this.rowMetaMap) {
                rowMeta = this.rowMetaMap.get(key);
                if (rowMeta == null) {
                    rowMeta = this.createRowContractMeta(queryInfo);

                    this.rowMetaMap.put(key, rowMeta);
                }
            }
        }

        return rowMeta;
    }

    public String getSchema() {
        return schema;
    }

    /**
     * 获取表
     *
     * @param tableName
     * @return
     */
    public TableDesc getTable(String tableName) {
        return tableMap.get(tableName);
    }

    @CollectionField
    public List<TableDesc> getTables() {
        return tables;
    }

    public void setDisplayName(String displayName) {
        this.displayName = displayName;
    }

    public void setMainTable(TableDesc mainTable) {
        this.mainTableName = mainTable.getName();
        this.mainTable = mainTable;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setSchema(String schema) {
        this.schema = schema;
    }

    @Override
    public String toString() {
        return StringUtils.isEmpty(this.displayName) ? this.name : this.displayName;
    }

    protected RowContractMeta createRowContractMeta(QueryInfo queryInfo) {
        RowContractMeta rowMeta = new RowContractMeta();
        rowMeta.setName(this.getName() + "RowContractMeta");
        rowMeta.setFlatReferenceFields(queryInfo.isSelectMode());

        for (SelectField selectField : queryInfo.resolveSelectFields(this)) {
            FieldDesc fieldDesc = this.getField(selectField.getFieldId(), true);
            IField field = fieldDesc.createField(queryInfo);

            if (field != null) {
                rowMeta.addField(field);
            }
        }

        return rowMeta;
    }

    private String genRowMetaKey(QueryInfo queryInfo) {
        String key = queryInfo.getSelectFields().toString() + "|" + queryInfo.getReferenceSelectFieldFetchMode();

        // key有查询字段构建，通常比较长，所以使用MD5编码作为key
        return DigestUtils.md5Hex(key);
    }

    private synchronized void indexMap() {
        if (this.fieldMap != null) {
            return;
        }

        this.fieldMap = new HashMap<String, FieldDesc>();

        for (TableDesc table : this.getTables()) {
            for (FieldDesc field : table.getFields()) {
                if (field.getId() == null) {
                    continue;
                }

                if (fieldMap.containsKey(field.getId())) {
                    throw ExceptionFactory.createRuntimeException("pisces-core.QueryMeta.indexMap-001", this.getName(),
                            field.getId());
                }

                fieldMap.put(field.getId(), field);
            }
        }

        for (TableDesc table : refTableMap.values()) {
            for (FieldDesc field : table.getFields()) {
                if (field.getId() == null) {
                    continue;
                }

                if (fieldMap.containsKey(field.getId())) {
                    throw ExceptionFactory.createRuntimeException("pisces-core.QueryMeta.indexMap-001", this.getName(),
                            field.getId());
                }
                fieldMap.put(field.getId(), field);
            }
        }
    }
}
